﻿using System;
using System.Runtime.InteropServices;

namespace NFox.Runtime.Com
{

    /// <summary>
    /// Com变体变量
    /// </summary>
    [StructLayout(LayoutKind.Explicit)]
    [System.Security.SecurityCritical]
    public struct Variant
    {

        #region Field

        [FieldOffset(0)]
        private TypeUnion _typeUnion;

        [FieldOffset(0)]
        private Decimal _decimal;

        [StructLayout(LayoutKind.Sequential)]
        private struct TypeUnion
        {
            internal ushort _vt;
            internal ushort _wReserved1;
            internal ushort _wReserved2;
            internal ushort _wReserved3;
            internal UnionTypes _unionTypes;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct Record
        {
            internal IntPtr _record;
            internal IntPtr _recordInfo;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")]
        [StructLayout(LayoutKind.Explicit)]
        private struct UnionTypes
        {

            #region Generated Variant union types

            // *** BEGIN GENERATED CODE ***
            // generated by function: gen_UnionTypes from: generate_comdispatch.py

            [FieldOffset(0)]
            internal SByte _i1;
            [FieldOffset(0)]
            internal Int16 _i2;
            [FieldOffset(0)]
            internal Int32 _i4;
            [FieldOffset(0)]
            internal Int64 _i8;
            [FieldOffset(0)]
            internal Byte _ui1;
            [FieldOffset(0)]
            internal UInt16 _ui2;
            [FieldOffset(0)]
            internal UInt32 _ui4;
            [FieldOffset(0)]
            internal UInt64 _ui8;
            [FieldOffset(0)]
            internal Int32 _int;
            [FieldOffset(0)]
            internal UInt32 _uint;
            [FieldOffset(0)]
            internal Int16 _bool;
            [FieldOffset(0)]
            internal UInt32 _error;
            [FieldOffset(0)]
            internal Single _r4;
            [FieldOffset(0)]
            internal Double _r8;
            [FieldOffset(0)]
            internal Int64 _cy;
            [FieldOffset(0)]
            internal double _date;

            [FieldOffset(0)]
            internal IntPtr _bstr;
            [FieldOffset(0)]
            internal IntPtr _unknown;
            [FieldOffset(0)]
            internal IntPtr _dispatch;

            // *** END GENERATED CODE ***

            #endregion

            [FieldOffset(0)]
            internal IntPtr _pvarVal;
            [FieldOffset(0)]
            internal IntPtr _byref;
            [FieldOffset(0)]
            internal Record _record;
        }

        #endregion

        #region Generated Variant accessors

        // *** BEGIN GENERATED CODE ***
        // generated by function: gen_accessors from: generate_comdispatch.py

        // VT_I1

        public SByte SByteValue
        {
            get { return _typeUnion._unionTypes._i1; }
            set { _typeUnion._unionTypes._i1 = value; }
        }

        // VT_I2

        public Int16 ShortValue
        {
            get { return _typeUnion._unionTypes._i2; }
            set { _typeUnion._unionTypes._i2 = value; }
        }

        // VT_I4

        public Int32 IntegerValue
        {
            get { return _typeUnion._unionTypes._i4; }
            set { _typeUnion._unionTypes._i4 = value; }
        }

        // VT_I8

        public Int64 LongValue
        {
            get { return _typeUnion._unionTypes._i8; }
            set { _typeUnion._unionTypes._i8 = value; }
        }

        // VT_UI1

        public Byte ByteValue
        {
            get { return _typeUnion._unionTypes._ui1; }
            set { _typeUnion._unionTypes._ui1 = value; }
        }

        // VT_UI2

        public UInt16 UShortValue
        {
            get { return _typeUnion._unionTypes._ui2; }
            set { _typeUnion._unionTypes._ui2 = value; }
        }

        // VT_UI4

        public UInt32 UIntegerValue
        {
            get { return _typeUnion._unionTypes._ui4; }
            set { _typeUnion._unionTypes._ui4 = value; }
        }

        // VT_UI8

        public UInt64 ULongValue
        {
            get { return _typeUnion._unionTypes._ui8; }
            set { _typeUnion._unionTypes._ui8 = value; }
        }

        // VT_BOOL

        public bool BoolValue
        {
            get { return _typeUnion._unionTypes._bool != 0; }
            set { _typeUnion._unionTypes._bool = value ? (short)-1 : (short)0; }
        }

        // VT_ERROR

        public UInt32 ErrorValue
        {
            get { return _typeUnion._unionTypes._error; }
            set { _typeUnion._unionTypes._error = value; }
        }

        // VT_R4

        public float FloatValue
        {
            get { return _typeUnion._unionTypes._r4; }
            set { _typeUnion._unionTypes._r4 = value; }
        }

        // VT_R8

        public double DoubleValue
        {
            get { return _typeUnion._unionTypes._r8; }
            set { _typeUnion._unionTypes._r8 = value; }
        }

        // VT_DECIMAL

        public decimal DecimalValue
        {
            // The first byte of Decimal is unused, but usually set to 0
            get { return _decimal; }
            // _vt overlaps with _decimal, and should be set after setting _decimal
            set { _decimal = value; }
        }

        // VT_CY

        public Decimal CyValue
        {
            get { return Decimal.FromOACurrency(_typeUnion._unionTypes._cy); }
            set { _typeUnion._unionTypes._cy = Decimal.ToOACurrency(value); }
        }

        // VT_DATE

        public DateTime DateValue
        {
            get { return DateTime.FromOADate(_typeUnion._unionTypes._date); }
            set { _typeUnion._unionTypes._date = value.ToOADate(); }
        }

        // VT_BSTR

        public String BStrValue
        {
            get { return (string)Marshal.PtrToStringBSTR(this._typeUnion._unionTypes._bstr); }
            set { this._typeUnion._unionTypes._bstr = Marshal.StringToBSTR(value); }
        }

        // VT_UNKNOWN

        public object UnknownObject
        {
            get
            {
                if (_typeUnion._unionTypes._unknown == IntPtr.Zero)
                    return null;
                return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._unknown);
            }
            set
            {
                if (value == null)
                    _typeUnion._unionTypes._unknown = IntPtr.Zero;
                else
                    _typeUnion._unionTypes._unknown = Marshal.GetIUnknownForObject(value);
            }
        }

        // VT_DISPATCH

        public object DispatchObject
        {
            get
            {
                if (_typeUnion._unionTypes._dispatch == IntPtr.Zero)
                    return null;
                return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._dispatch);
            }
            set
            {
                if (value == null)
                    _typeUnion._unionTypes._dispatch = IntPtr.Zero;
                else
                    _typeUnion._unionTypes._dispatch = Marshal.GetIDispatchForObject(value);
            }
        }


        // *** END GENERATED CODE ***

        public IntPtr RefValue
        {
            get { return _typeUnion._unionTypes._byref; }
            set { _typeUnion._unionTypes._byref = value; }
        }

        #endregion

        public VarEnum VariantType
        {
            get { return (VarEnum)_typeUnion._vt; }
            set { _typeUnion._vt = (ushort)value; }
        }


        public bool IsEmpty
        {
            get { return _typeUnion._vt == ((ushort)VarEnum.VT_EMPTY); }
        }

        public bool IsByRef
        {
            get { return (_typeUnion._vt & ((ushort)VarEnum.VT_BYREF)) != 0; }
        }

        public void SetAsNULL()
        {
            VariantType = VarEnum.VT_NULL;
        }

        [System.Security.SuppressUnmanagedCodeSecurity,
        DllImport("oleaut32.dll", PreserveSig = false),
        System.Security.SecurityCritical]
        static extern void VariantClear(IntPtr variant);

        /// <summary>
        /// Release any unmanaged memory associated with the Variant
        /// </summary>
        /// <returns></returns>
        public void Clear()
        {
            // We do not need to call OLE32's VariantClear for primitive types or ByRefs
            // to safe ourselves the cost of interop transition.
            // ByRef indicates the memory is not owned by the VARIANT itself while
            // primitive types do not have any resources to free up.
            // Hence, only safearrays, BSTRs, interfaces and user types are 
            // handled differently.
            VarEnum vt = VariantType;
            if ((vt & VarEnum.VT_BYREF) != 0)
            {
                VariantType = VarEnum.VT_EMPTY;
            }
            else if (
              ((vt & VarEnum.VT_ARRAY) != 0) ||
              (vt == VarEnum.VT_BSTR) ||
              (vt == VarEnum.VT_UNKNOWN) ||
              (vt == VarEnum.VT_DISPATCH) ||
              (vt == VarEnum.VT_VARIANT) ||
              (vt == VarEnum.VT_RECORD) ||
              (vt == VarEnum.VT_VARIANT)
              )
            {
                unsafe
                {
                    fixed (void* pThis = &this)
                    {
                        VariantClear((IntPtr)pThis);
                    }
                }
            }
            else
            {
                VariantType = VarEnum.VT_EMPTY;
            }
        }

        /// <summary>
        /// Get the managed object representing the Variant.
        /// </summary>
        /// <returns></returns>
        public object ToObject()
        {
            // Check the simple case upfront
            if (IsEmpty)
            {
                return null;
            }

            switch (VariantType)
            {
                case VarEnum.VT_NULL: return DBNull.Value;

                #region Generated Variant ToObject

                // *** BEGIN GENERATED CODE ***
                // generated by function: gen_ToObject from: generate_comdispatch.py

                case VarEnum.VT_I1: return SByteValue;
                case VarEnum.VT_I2: return ShortValue;
                case VarEnum.VT_I4: return IntegerValue;
                case VarEnum.VT_I8: return LongValue;
                case VarEnum.VT_UI1: return ByteValue;
                case VarEnum.VT_UI2: return UShortValue;
                case VarEnum.VT_UI4: return UIntegerValue;
                case VarEnum.VT_UI8: return ULongValue;
                case VarEnum.VT_INT: return IntegerValue;
                case VarEnum.VT_UINT: return UIntegerValue;
                case VarEnum.VT_BOOL: return BoolValue;
                case VarEnum.VT_ERROR: return ErrorValue;
                case VarEnum.VT_R4: return FloatValue;
                case VarEnum.VT_R8: return DoubleValue;
                case VarEnum.VT_DECIMAL: return DecimalValue;
                case VarEnum.VT_CY: return CyValue;
                case VarEnum.VT_DATE: return DateValue;
                case VarEnum.VT_BSTR: return BStrValue;
                case VarEnum.VT_UNKNOWN: return UnknownObject;
                case VarEnum.VT_DISPATCH: return DispatchObject;
                // VarEnum.VT_VARIANT is handled by Marshal.GetObjectForNativeVariant below

                // *** END GENERATED CODE ***

                #endregion

                default:
                    try
                    {
                        unsafe
                        {
                            fixed (void* pThis = &this)
                            {
                                return Marshal.GetObjectForNativeVariant((System.IntPtr)pThis);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new NotImplementedException("Variant.ToObject cannot handle" + VariantType, ex);
                    }
            }
        }

        unsafe public void CopyFromIndirect(object value)
        {

            VarEnum vt = (VarEnum)(((int)this.VariantType) & ~((int)VarEnum.VT_BYREF));

            if (value == null)
            {
                if (vt == VarEnum.VT_DISPATCH || vt == VarEnum.VT_UNKNOWN || vt == VarEnum.VT_BSTR)
                {
                    *(IntPtr*)this._typeUnion._unionTypes._byref = IntPtr.Zero;
                }
                return;
            }

            switch (vt)
            {
                case VarEnum.VT_I1:
                    *(sbyte*)this._typeUnion._unionTypes._byref = (sbyte)value;
                    break;

                case VarEnum.VT_UI1:
                    *(byte*)this._typeUnion._unionTypes._byref = (byte)value;
                    break;

                case VarEnum.VT_I2:
                    *(short*)this._typeUnion._unionTypes._byref = (short)value;
                    break;

                case VarEnum.VT_UI2:
                    *(ushort*)this._typeUnion._unionTypes._byref = (ushort)value;
                    break;

                case VarEnum.VT_BOOL:
                    *(short*)this._typeUnion._unionTypes._byref = (bool)value ? (short)-1 : (short)0;
                    break;

                case VarEnum.VT_I4:
                case VarEnum.VT_INT:
                    *(int*)this._typeUnion._unionTypes._byref = (int)value;
                    break;

                case VarEnum.VT_UI4:
                case VarEnum.VT_UINT:
                    *(uint*)this._typeUnion._unionTypes._byref = (uint)value;
                    break;

                case VarEnum.VT_ERROR:
                    *(int*)this._typeUnion._unionTypes._byref = ((ErrorWrapper)value).ErrorCode;
                    break;

                case VarEnum.VT_I8:
                    *(Int64*)this._typeUnion._unionTypes._byref = (Int64)value;
                    break;

                case VarEnum.VT_UI8:
                    *(UInt64*)this._typeUnion._unionTypes._byref = (UInt64)value;
                    break;

                case VarEnum.VT_R4:
                    *(float*)this._typeUnion._unionTypes._byref = (float)value;
                    break;

                case VarEnum.VT_R8:
                    *(double*)this._typeUnion._unionTypes._byref = (double)value;
                    break;

                case VarEnum.VT_DATE:
                    *(double*)this._typeUnion._unionTypes._byref = ((DateTime)value).ToOADate();
                    break;

                case VarEnum.VT_UNKNOWN:
                    *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.GetIUnknownForObject(value);
                    break;

                case VarEnum.VT_DISPATCH:
                    *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.GetIDispatchForObject(value);
                    break;

                case VarEnum.VT_BSTR:
                    *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.StringToBSTR((string)value);
                    break;

                case VarEnum.VT_CY:
                    *(long*)this._typeUnion._unionTypes._byref = decimal.ToOACurrency((decimal)value);
                    break;

                case VarEnum.VT_DECIMAL:
                    *(decimal*)this._typeUnion._unionTypes._byref = (decimal)value;
                    break;

                case VarEnum.VT_VARIANT:
                    Marshal.GetNativeVariantForObject(value, this._typeUnion._unionTypes._byref);
                    break;

                default:
                    throw new ArgumentException("invalid argument type");
            }
        }

    }
}
